package com.jojo.bubble.controller.socket;

import com.alibaba.fastjson.JSON;
import com.jojo.bubble.common.domain.AjaxResult;
import com.jojo.bubble.entity.Dev;
import com.jojo.bubble.entity.DevVo;
import com.jojo.bubble.service.DevService;
import com.jojo.bubble.service.UserService;
import com.jojo.bubble.utils.ApplicationContextUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseProperties;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.w3c.dom.css.CSSStyleRule;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;


/**
 * @author JoJo
 * @Date 2022/7/21 9:37
 **/
@RestController
@RequestMapping("/api/server")
@Slf4j
public class Server {

    @Resource
    DevService thisDevService;
    @Resource
    UserService thisUserService;
    /**
     * 客户端集合
     */
    private static List<Socket> clientList = new ArrayList<>();


    public static ConcurrentMap<String, Socket> existSocketClientMap = new ConcurrentHashMap<>();

    /**
     * 客户端心跳时间集合
     */
    private static Map<Socket, Date> heartbeatMap = new HashMap<>(16);

    /**
     * 协议状态的集合
     */
    private static ConcurrentMap<String, Integer> protocolStateMap = new ConcurrentHashMap<>();

    /**
     * 心跳超时时间
     */
//    private static final long TIMEOUT = 20 * 1000;
    private static final long TIMEOUT = 20 * 10000000;

    /**
     * 请求最长等待次数
     */
    private static final int COUNT = 300;

    /**
     * 请求每次查询间隔(毫秒)
     */
    private static final int DELAY = 1000;


    /**
     * 服务端端口
     */
    private static final int PORT = 8083;

    /**
     * 以下为与客户端约定的指令，分别是：心跳、心跳回执、退出和退出回执
     */
    private static final String HEARTBEAT = "heartbeat";
    private static final String HEARTBEAT_RECEIPT = "heartbeat_receipt";
    private static final String EXIT = "exit";
    private static final String EXIT_RECEIPT = "exit_receipt";

    public static void main(String[] args) {
        new Server().start();
    }

    /**
     * 功能描述:
     * <服务端启动>
     *
     * @return void
     * @author zhoulipu
     * @date 2019/8/8 15:52
     */
    public void start() {
        try {
            // 服务端开启
            ServerSocket server = new ServerSocket(PORT);
            System.out.println("服务端开启，等待客户端连接中...");
            // 循环监听客户端连接
            while (true) {
                // 等待客户端进行连接
                Socket client = server.accept();
                // 将客户端添加到集合
                clientList.add(client);
                System.out.println("有建立连接了，客户端地址：" + client.getRemoteSocketAddress().toString().replace("/", "") + "，当前连接数量：" + clientList.size());
                // 添加首次连接时间作为心跳
                heartbeatMap.put(client, new Date());
                // 开启新线程处理消息
                new MessageListener(client).start();
                // 开启新线程监测心跳
                new HeartbeatListener(client).start();
            }
        } catch (IOException e) {
            e.getStackTrace();
        }
    }

    /**
     * @return com.jojo.bubble.common.domain.AjaxResult
     * @desc 检查设备连接状态
     * @Date 2023/7/27 17:39
     * @Param [devVo]
     */
    @PostMapping("/checkDev")
    public AjaxResult checkDev(DevVo devVo) {
        Socket client = existSocketClientMap.get(devVo.getDevCode());
        if (client == null) {
            return AjaxResult.error("设备未连接");
        }
        return AjaxResult.success("设备已连接");
    }

    /**
     * @return com.jojo.bubble.common.domain.AjaxResult
     * @desc 运行设备
     * @Date 2023/7/27 17:39
     * @Param [devVo]
     */
    @PostMapping("/openDev")
    public AjaxResult openDev(DevVo devVo) throws IOException {
        Socket client = existSocketClientMap.get(devVo.getDevCode());
        if (client == null) {
            return AjaxResult.error("设备未连接");
        }
        Dev dev = thisDevService.queryByCode(devVo.getDevCode());
        if (ObjectUtils.isEmpty(dev)) {
            return AjaxResult.error("设备未连接");
        } else {
            if (dev.getStatus() == 2) {
                return AjaxResult.error("SIM被修改无法使用");
            }
        }
        if (!StringUtils.hasLength(devVo.getTimeStr())) {
            devVo.setTimeStr("01");
        }
        if (Integer.valueOf(devVo.getTimeStr()) > 254) {
            devVo.setTimeStr("255");
        }
        sendMsg(client, "8605" + devVo.getDevCode() + devVo.getTimeStr());
        String code = "8606" + devVo.getDevCode();

        return handleSetReq(code);

    }


    @PostMapping("/openAll")
    public AjaxResult openDevAll(DevVo devVo) throws IOException {
        String[] idsStr = devVo.getIds().split(",");
        if (!StringUtils.hasLength(devVo.getTimeStr())) {
            devVo.setTimeStr("01");
        }
        if (Integer.valueOf(devVo.getTimeStr()) > 254) {
            devVo.setTimeStr("255");
        }
        for (String id : idsStr) {
            Dev dev = thisDevService.getInfo(Integer.valueOf(id));
            if (!ObjectUtils.isEmpty(dev)) {
                if (dev.getStatus() == 1) {
                    Socket client = existSocketClientMap.get(dev.getCode());
                    if (client != null) {
                        sendMsg(client, "8605" + dev.getCode() + devVo.getTimeStr());
                    }
                }
            }
        }
        return AjaxResult.success();
    }

    @PostMapping("/stopAll")
    public AjaxResult stopDevAll(String ids) throws IOException {
        String[] idsStr = ids.split(",");
        for (String id : idsStr) {
            Dev dev = thisDevService.getInfo(Integer.valueOf(id));
            Socket client = existSocketClientMap.get(dev.getCode());
            if (client != null) {
                sendMsg(client, "8607" + dev.getCode());
            }
        }
        return AjaxResult.success();
    }

    /**
     * @return com.jojo.bubble.common.domain.AjaxResult
     * @desc 关闭设备
     * @Date 2023/7/27 17:40
     * @Param [devCode]
     */
    @PostMapping("/stopDev")
    public AjaxResult stopDev(String devCode) throws IOException {
        Socket client = existSocketClientMap.get(devCode);
        if (client == null) {
            return AjaxResult.error("设备未连接");
        }
        sendMsg(client, "8607" + devCode);
        String code = "8608" + devCode;


        return handleSetReq(code);

    }


    @PostMapping("/openLed")
    public AjaxResult openLed(String devCode) throws IOException {
        Socket client = existSocketClientMap.get(devCode);
        if (client == null) {
            return AjaxResult.error("设备未连接");
        }
        String code = "8609" + devCode;
        sendMsg(client, code);


        return handleSetReq(code);
    }

    @PostMapping("/openAllLed")
    public AjaxResult openLedAll(String ids) throws IOException {
        String[] idsStr = ids.split(",");
        for (String id : idsStr) {
            Dev dev = thisDevService.getInfo(Integer.valueOf(id));
            Socket client = existSocketClientMap.get(dev.getCode());
            if (client != null) {
                sendMsg(client, "8609" + dev.getCode());
            }
        }
        return AjaxResult.success();
    }

    @PostMapping("/stopAllLed")
    public AjaxResult stopLedAll(String ids) throws IOException {
        String[] idsStr = ids.split(",");
        for (String id : idsStr) {
            Dev dev = thisDevService.getInfo(Integer.valueOf(id));
            Socket client = existSocketClientMap.get(dev.getCode());
            if (client != null) {
                sendMsg(client, "8610" + dev.getCode());
            }
        }
        return AjaxResult.success();
    }

    @PostMapping("/stopLed")
    public AjaxResult stopLed(String devCode) throws IOException {
        Socket client = existSocketClientMap.get(devCode);
        if (client == null) {
            return AjaxResult.error("设备未连接");
        }
        String code = "8610" + devCode;

        sendMsg(client, code);

        return handleSetReq(code);
    }

    //自动模式开启
    @PostMapping("/openAutoMode")
    public AjaxResult openAutoMode(DevVo devVo) throws IOException {
        Socket client = existSocketClientMap.get(devVo.getDevCode());
        if (client == null) {
            return AjaxResult.error("设备未连接");
        }
        Dev dev = thisDevService.queryByCode(devVo.getDevCode());
        if (ObjectUtils.isEmpty(dev)) {
            return AjaxResult.error("设备未连接");
        } else {
            if (dev.getStatus() == 2) {
                return AjaxResult.error("SIM被修改无法使用");
            }
        }
        if (!StringUtils.hasLength(devVo.getTimeStr())) {
            devVo.setTimeStr("01");
        }
        if (Integer.valueOf(devVo.getTimeStr()) > 254) {
            devVo.setTimeStr("255");
        }
        String code = "8611" + devVo.getDevCode();

        sendMsg(client, code + devVo.getTimeStr());


        return handleSetReq(code);
    }

    //设置电机转速
    @PostMapping("/setMotorSpeed")
    public AjaxResult setMotorSpeed(String devCode, String speed) throws IOException {
        Socket client = existSocketClientMap.get(devCode);
        if (client == null) {
            return AjaxResult.error("设备未连接");
        }
        String code = "8620" + devCode;

        sendMsg(client, code + speed);
        return handleSetReq(code);
    }

    //设置所有电机转速
    @PostMapping("/setAllMotorSpeed")
    public AjaxResult setAllMotorSpeed(String ids, String speed) throws IOException {
        String[] idsStr = ids.split(",");
        for (String id : idsStr) {
            Dev dev = thisDevService.getInfo(Integer.valueOf(id));
            Socket client = existSocketClientMap.get(dev.getCode());
            if (client != null) {
                sendMsg(client, "8620" + dev.getCode() + speed);
            }
        }
        return AjaxResult.success();
    }

    //查询电机转速
    @PostMapping("/getMotorSpeed")
    public AjaxResult getMotorSpeed(String devCode) throws IOException {
        Socket client = existSocketClientMap.get(devCode);
        if (client == null) {
            return AjaxResult.error("设备未连接");
        }
        String code = "8621" + devCode;
        sendMsg(client, code);
        return handleGetReq(code);
    }

    //设置喇叭音量
    @PostMapping("/setSpeakerVolume")
    public AjaxResult setSpeakerVolume(String devCode, String volume) throws IOException {
        Socket client = existSocketClientMap.get(devCode);
        if (client == null) {
            return AjaxResult.error("设备未连接");
        }
        String code = "8622" + devCode;

        sendMsg(client, code + volume);

        return handleSetReq(code);
    }

    //设置所有喇叭音量
    @PostMapping("/setAllSpeakerVolume")
    public AjaxResult setAllSpeakerVolume(String ids, String volume) throws IOException {
        String[] idsStr = ids.split(",");
        for (String id : idsStr) {
            Dev dev = thisDevService.getInfo(Integer.valueOf(id));
            Socket client = existSocketClientMap.get(dev.getCode());
            if (client != null) {
                sendMsg(client, "8622" + dev.getCode() + volume);
            }
        }
        return AjaxResult.success();
    }

    //查询喇叭音量
    @PostMapping("/getSpeakerVolume")
    public AjaxResult getSpeakerVolume(String devCode) throws IOException {
        Socket client = existSocketClientMap.get(devCode);
        if (client == null) {
            return AjaxResult.error("设备未连接");
        }
        String code = "8623" + devCode;

        sendMsg(client, code);

        return handleGetReq(code);
    }

    //设置液位功能
    @PostMapping("/setLiquidLevel")
    public AjaxResult setLiquidLevel(String devCode, String flag) throws IOException {
        Socket client = existSocketClientMap.get(devCode);
        if (client == null) {
            return AjaxResult.error("设备未连接");
        }
        String code = "8624" + devCode;
        sendMsg(client, code + flag);
        return handleSetReq(code);
    }

    //设置所有液位功能
    @PostMapping("/setAllLiquidLevel")
    public AjaxResult setAllLiquidLevel(String ids, String flag) throws IOException {
        String[] idsStr = ids.split(",");
        for (String id : idsStr) {
            Dev dev = thisDevService.getInfo(Integer.valueOf(id));
            Socket client = existSocketClientMap.get(dev.getCode());
            if (client != null) {
                sendMsg(client, "8624" + dev.getCode() + flag);
            }
        }
        return AjaxResult.success();
    }

    //查询液位功能
    @PostMapping("/getLiquidLevel")
    public AjaxResult getLiquidLevel(String devCode) throws IOException {
        Socket client = existSocketClientMap.get(devCode);
        if (client == null) {
            return AjaxResult.error("设备未连接");
        }
        String code = "8625" + devCode;
        sendMsg(client, code);

        protocolStateMap.put(code, 0);

        int count = 0;
        while (true) {
            try {
                Thread.sleep(DELAY);//单位：毫秒
                count++;
                if (count > COUNT) return AjaxResult.error("请求超时");
                switch (protocolStateMap.get(code)) {
                    case 0:
                        break;
                    case 1:
                        return AjaxResult.success("01");
                    case 2:
                        return AjaxResult.success("00");
                    default:
                        return AjaxResult.error("未知错误");
                }
            } catch (Exception e) {
                return AjaxResult.error("未知错误");
            }

        }
    }


    /**
     * 消息处理
     */
    class MessageListener extends Thread {

        private Socket client;

        DevService devService = ApplicationContextUtil.getBean(DevService.class);

        public MessageListener(Socket socket) {
            this.client = socket;

        }

        @Override
        public void run() {
            try {
                // 客户端连接后立即下发消息
                sendMsg(client, "Connection Success");
                String message;
                String msg;
                // 当心跳存在时，循环处理消息 heartbeatMap.get(client) != null
                while (heartbeatMap.get(client) != null) {
                    // 读取客户端消息
                    Thread.sleep(100);
                    message = receiveMsg(client).trim();
                    msg = message.replaceAll(" +", "");
                    if (StringUtils.hasLength(msg)) {
                        if (!msg.substring(0, 2).equals("86")) {
                            sendMsg(client, "Incompatible Data");
                            client.close();
                        } else {

                            if (msg.substring(0, 4).equals("8602")) {
                                // 记录客户端的心跳时间
                                heartbeatMap.put(client, new Date());
//                                log.info("msg:" + msg);
                                String devCode = msg.substring(4, 19);
                                String simId = msg.substring(19, msg.length() - 7);
//                                log.info("simId:" + simId);
                                String sNum = msg.substring(msg.length() - 7, msg.length() - 5);
                                String devStatus = msg.substring(msg.length() - 5, msg.length() - 3);
                                String devPower = msg.substring(msg.length() - 3);
                                existSocketClientMap.put(devCode, client);
                                Dev dev = new Dev();
                                dev.setCode(devCode);
                                dev.setName(simId);
                                dev.setSNum(Integer.valueOf(sNum));
                                dev.setPNum(Integer.valueOf(devStatus));
                                dev.setENum(Integer.valueOf(devPower));
                                dev.setOwnAgentId(0);
                                dev.setAgentId(0);
                                devService.checkDev(dev);

//                                System.out.println("msg:"+msg+"设备号:"+devCode+"devStatus:"+devStatus+"devPower"+devPower);
                            } else if (msg.substring(0, 2).equals("86")) {
                                log.info(msg);
                                String key = msg.substring(0, 4);
                                String code = msg.substring(0, 19);
                                String result = null;

                                switch (key) {
                                    case "8606":
                                        System.out.println("请求开启反馈");
                                        result = msg.substring(msg.length() - 4, msg.length() - 2);
                                        if (result.equals("00")) {
                                            protocolStateMap.put(code, 3);
                                        } else if (result.equals("01")) {
                                            protocolStateMap.put(code, 2);
                                        }
                                        break;
                                    case "8608":
                                        System.out.println("请求关闭反馈");
                                        result = msg.substring(msg.length() - 2);
                                        if (result.equals("00")) {
                                            protocolStateMap.put(code, 3);
                                        } else if (result.equals("01")) {
                                            protocolStateMap.put(code, 2);
                                        }
                                        break;
                                    case "8609":
                                        System.out.println("远程开灯请求反馈");
                                        result = msg.substring(msg.length() - 2);
                                        if (result.equals("00")) {
                                            protocolStateMap.put(code, 3);
                                        } else if (result.equals("01")) {
                                            protocolStateMap.put(code, 2);
                                        }
                                        break;
                                    case "8610":
                                        System.out.println("远程关灯请求反馈");
                                        result = msg.substring(msg.length() - 2);
                                        if (result.equals("00")) {
                                            protocolStateMap.put(code, 3);
                                        } else if (result.equals("01")) {
                                            protocolStateMap.put(code, 2);
                                        }
                                        break;
                                    case "8611":
                                        System.out.println("自动模式开启请求反馈");
                                        result = msg.substring(msg.length() - 2);
                                        if (result.equals("00")) {
                                            protocolStateMap.put(code, 3);
                                        } else if (result.equals("01")) {
                                            protocolStateMap.put(code, 2);
                                        }
                                        break;
                                    case "8620":
                                        System.out.println("设置电机转速请求反馈");
                                        result = msg.substring(msg.length() - 2);
                                        if (result.equals("00")) {
                                            protocolStateMap.put(code, 3);
                                        } else if (result.equals("01")) {
                                            protocolStateMap.put(code, 2);
                                        }
                                        break;
                                    case "8621":
                                        System.out.println("查询电机转速请求反馈");
                                        result = msg.substring(msg.length() - 2);
                                        if (result.equals("01")) {
                                            protocolStateMap.put(code, 1);
                                        } else if (result.equals("02")) {
                                            protocolStateMap.put(code, 2);
                                        } else if (result.equals("03")) {
                                            protocolStateMap.put(code, 3);
                                        }
                                        break;
                                    case "8622":
                                        System.out.println("设置喇叭音量请求反馈");
                                        result = msg.substring(msg.length() - 2);
                                        if (result.equals("00")) {
                                            protocolStateMap.put(code, 3);
                                        } else if (result.equals("01")) {
                                            protocolStateMap.put(code, 2);
                                        }
                                        break;
                                    case "8623":
                                        System.out.println("查询喇叭音量请求反馈");
                                        result = msg.substring(msg.length() - 2);
                                        if (result.equals("01")) {
                                            protocolStateMap.put(code, 1);
                                        } else if (result.equals("02")) {
                                            protocolStateMap.put(code, 2);
                                        } else if (result.equals("03")) {
                                            protocolStateMap.put(code, 3);
                                        }
                                        break;
                                    case "8624":
                                        System.out.println("设置液位功能请求反馈");
                                        result = msg.substring(msg.length() - 2);
                                        if (result.equals("00")) {
                                            protocolStateMap.put(code, 3);
                                        } else if (result.equals("01")) {
                                            protocolStateMap.put(code, 2);
                                        }
                                        break;
                                    case "8625":
                                        System.out.println("查询液位功能请求反馈");
                                        result = msg.substring(msg.length() - 2);
                                        if (result.equals("00")) {
                                            protocolStateMap.put(code, 2);
                                        } else if (result.equals("01")) {
                                            protocolStateMap.put(code, 1);
                                        }
                                        break;
                                    default:
                                        System.out.println("未知指令");
                                        break;
                                }
                                System.out.println(code);


                            }

                        }
//                        System.out.println(existSocketClientMap);
                    } else {
                        Thread.sleep(100);
                    }


                }
            } catch (IOException | InterruptedException e) {
                e.getStackTrace();
            }
        }
    }

    /**
     * 心跳监测
     */
    class HeartbeatListener extends Thread {

        private Socket client;
        DevService devService = ApplicationContextUtil.getBean(DevService.class);

        private HeartbeatListener(Socket socket) {
            this.client = socket;
        }

        @SneakyThrows
        @Override
        public void run() {
            Date time, now;
            // 当心跳存在时，循环处理消息
            while ((time = heartbeatMap.get(client)) != null) {
                Thread.sleep(100);
                now = new Date();
                // 比对当前时间和最新心跳时间
                if (now.getTime() - time.getTime() > TIMEOUT) {
                    // 客户端心跳超时（这里当作断开连接处理），删除连接和心跳

                    for (String k : existSocketClientMap.keySet()) {
//                        System.out.println(k);
                        if (existSocketClientMap.get(k).equals(client)) {
                            System.out.println("断开设备:" + k);
                            devService.cleanDev(k);
                            existSocketClientMap.remove(k);
                        }
                    }
                    heartbeatMap.remove(client);
                    clientList.remove(client);
                    // 关闭连接
                    try {
                        client.close();
                        Thread.yield();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
//                    Set<String> keySet = new LinkedHashSet<>();
//                    for (String k : existSocketClientMap.keySet()) {
//                        if (existSocketClientMap.get(k).equals(client)) {
//                            keySet.add(k);
//                        }
//                    }

                    System.out.println("有心跳超时了，客户端地址：" + client.getRemoteSocketAddress().toString().replace("/", "") + "，当前连接数量：" + clientList.size());
                }
            }
        }
    }

    /**
     * 功能描述:
     * <发送消息>
     */
    private void sendMsg(Socket socket, String msg) throws IOException {
        OutputStream out = socket.getOutputStream();
        PrintWriter writer = new PrintWriter(out);
        // 使用pw.write(msg); msg末尾必须加"\n"转义， println自动添加转义
        writer.println(msg);
        writer.flush();
    }

    /**
     * 功能描述:
     * <接受消息>
     */
    private String receiveMsg(Socket socket) throws IOException {
        InputStream in = socket.getInputStream();
        byte b[] = new byte[1024];
        in.read(b);
        String msg = new String(b);
        return msg;
    }

    /**
     * 功能描述:
     * <处理设置的请求>
     */
    private AjaxResult handleSetReq(String code) {
        protocolStateMap.put(code, 1);

        int count = 0;
        while (true) {
            try {
                Thread.sleep(DELAY);//单位：毫秒
                count++;
                if (count > COUNT) return AjaxResult.error("请求超时");
                switch (protocolStateMap.get(code)) {
                    case 1:
                        break;
                    case 2:
                        return AjaxResult.success();
                    case 3:
                        return AjaxResult.error("请求失败");
                    default:
                        return AjaxResult.error("未知错误");
                }
            } catch (Exception e) {
                return AjaxResult.error("1");
            }

        }
    }


    /**
     * 功能描述:
     * <处理查询的请求>
     */
    private AjaxResult handleGetReq(String code) {
        protocolStateMap.put(code, 0);

        int count = 0;
        while (true) {
            try {
                Thread.sleep(DELAY);//单位：毫秒
                count++;
                if (count > COUNT) return AjaxResult.error("请求超时");
                switch (protocolStateMap.get(code)) {
                    case 0:
                        break;
                    case 1:
                        return AjaxResult.success(1);
                    case 2:
                        return AjaxResult.success(2);
                    case 3:
                        return AjaxResult.success(3);
                    default:
                        return AjaxResult.error("未知错误");
                }
            } catch (Exception e) {
                return AjaxResult.error("未知错误");
            }

        }
    }

}
